#ifndef __HYPERMAP_H__
#define __HYPERMAP_H__

#include <iostream>
#include <map>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <stdexcept>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>

using std::hash;
using std::lock_guard;
using std::make_shared;
using std::mutex;
using std::shared_ptr;
using std::string;
using std::tuple;
using std::unordered_map;
using std::vector;

using std::nullopt;
using std::optional;
using std::shared_lock;
using std::shared_timed_mutex;
using std::unique_lock;

namespace hyperex::magicMap {

enum class insertType { New = 0, Update, Donothing, Fail };

template <typename key_t, typename value_t> class magicMap {
public:
  using keyType = key_t;
  using valueRawType = value_t;
  using valueType = shared_ptr<value_t>;
  using mapType = unordered_map<keyType, valueType>;
  using eleType = typename mapType::value_type;
  using vectorType = vector<valueType>;

private:
  mapType m;
  mutable std::shared_timed_mutex mtx;

public:
  magicMap() {}
  ~magicMap() {}

  tuple<valueType, insertType> insert(const eleType &ele) {
    auto ke = ele.first;
    auto re = this->find(ke);

    if (re) {
      if (*re == *(ele.second)) {
        return {re, insertType::Donothing};

      } else {
        std::unique_lock<std::shared_timed_mutex> lk(mtx);
        m.insert_or_assign(ele.first, ele.second);
        return {m[ke], insertType::Update};
      }
    }
    try {
      std::unique_lock<std::shared_timed_mutex> lk(mtx);
      m.insert_or_assign(ele.first, ele.second);
      return {m[ke], insertType::New};
    } catch (...) {
      return {nullptr, insertType::Fail};
    }
  }
  valueType find(const keyType &ke) {
    std::shared_lock<std::shared_timed_mutex> lk(mtx);
    auto itr = m.find(ke);
    if (itr == m.end())
      return nullptr;
    return itr->second;
  }

  template <typename... Args> valueType findExpand(Args... args) {

    std::shared_lock<std::shared_timed_mutex> lk(mtx);
    for (const auto &iter : m) {
      if (iter.second->match(args...))
        return iter.second;
    }
    return nullptr;
  }

  auto size() const { return m.size(); }

  void erase(const keyType &ke) {
    std::unique_lock<std::shared_timed_mutex> lk(mtx);
    auto itr = m.find(ke);
    if (itr == m.end())
      return;
    m.erase(itr);
  }

  void clear() {
    std::unique_lock<std::shared_timed_mutex> lk(mtx);
    m.clear();
  }

  vectorType getAllValues() {
    std::unique_lock<std::shared_timed_mutex> lk(mtx);
    vectorType vec;
    for (const auto &_ : m) {
      vec.push_back(_.second);
    }

    return std::move(vec);
  }
};

template <typename key_t, typename value_t> class easyMap {
public:
  using keyType = key_t;
  using valueType = value_t;
  using mapType = unordered_map<keyType, valueType>;
  using eleType = typename mapType::value_type;
  using vectorType = vector<valueType>;
  using veceleType = vector<std::pair<key_t, value_t>>;

public:
  easyMap() {}
  ~easyMap() { _m.clear(); }

private:
  mapType _m;
  mutable std::shared_timed_mutex _mtx;

public:
  auto insert(const eleType &e) -> std::optional<string> {
    auto ke = e.first;
    auto [v, err] = this->find(ke);
    if (v)
      return "insert fail";

    std::unique_lock<std::shared_timed_mutex> lk(_mtx);
    _m.insert_or_assign(e.first, e.second);
    return std::nullopt;
  }

  auto insert_or_assign(const eleType &e) -> std::optional<string> {
    std::unique_lock<std::shared_timed_mutex> lk(_mtx);
    _m.insert_or_assign(e.first, e.second);
    return std::nullopt;
  }

  void insert_vec(const veceleType &v) {
    std::unique_lock<std::shared_timed_mutex> lk(_mtx);
    _m.clear();
    std::copy(v.begin(), v.end(), std::inserter(_m, _m.begin()));
  }

  auto find(const keyType &k) -> std::tuple<std::optional<valueType>, string> {
    std::shared_lock<std::shared_timed_mutex> lk(_mtx);
    auto iter = _m.find(k);
    if (iter == _m.end())
      return {std::nullopt, "not found"};

    return {iter->second, ""};
  }

  void erase(const keyType &k) {
    std::unique_lock<std::shared_timed_mutex> lk(_mtx);
    auto iter = _m.find(k);
    if (iter != _m.end())
      _m.erase(iter);
  }

  void clear() {
    std::unique_lock<std::shared_timed_mutex> lk(_mtx);
    _m.clear();
  }

  int64_t size() {
    std::shared_lock<std::shared_timed_mutex> lk(_mtx);
    return _m.size();
  }

  vectorType getAllValues() {
    std::unique_lock<std::shared_timed_mutex> lk(_mtx);
    vectorType vec;
    for (const auto &_ : _m) {
      vec.push_back(_.second);
    }

    return std::move(vec);
  }
};

template <typename key_t, typename value_t> class EasyBitMap {
public:
  using keyType = key_t;
  using valueType = value_t;
  using leftMapType = unordered_map<keyType, valueType>;
  using rightMapType = unordered_map<valueType, keyType>;
  using leftElemType = typename leftMapType::value_type;
  using rightElemType = typename rightMapType::value_type;

public:
  EasyBitMap() {}
  ~EasyBitMap() {}

public:
  bool insert(const leftElemType &elem) {
    unique_lock<shared_timed_mutex> lk(mtx_);
    if (leftMap_.find(elem.first) != leftMap_.end() ||
        rightMap_.find(elem.second) != rightMap_.end())
      return false;

    leftMap_.insert(elem);
    rightMap_.insert(rightElemType(elem.second, elem.first));
    return true;
  }

  void insertAndCover(const leftElemType &elem) {
    unique_lock<shared_timed_mutex> lk(mtx_);
    leftMap_.insert_or_assign(elem.first, elem.second);
    rightMap_.insert_or_assign(elem.second, elem.first);
  }

  auto findLeft(const keyType &ke) -> optional<valueType> {
    shared_lock<shared_timed_mutex> lk(mtx_);
    auto iter = leftMap_.find(ke);
    if (iter == leftMap_.end())
      return nullopt;
    return iter->second;
  }

  auto findRight(const valueType &value) -> optional<keyType> {
    shared_lock<shared_timed_mutex> lk(mtx_);
    auto iter = rightMap_.find(value);
    if (iter == rightMap_.end())
      return nullopt;
    return iter->second;
  }

  bool removeByLeft(const keyType &ke) {

    unique_lock<shared_timed_mutex> lk(mtx_);
    auto iterLeft = leftMap_.find(ke);
    if (iterLeft == leftMap_.end())
      return false;
    auto iterRight = rightMap_.find(iterLeft->second);
    if (iterRight == rightMap_.end())
      return false;

    leftMap_.erase(iterLeft);
    rightMap_.erase(iterRight);
    return true;
  }

  bool removeByRight(const valueType &value) {
    unique_lock<shared_timed_mutex> lk(mtx_);
    auto iterRight = rightMap_.find(value);
    if (iterRight == rightMap_.end())
      return false;
    auto iterLeft = leftMap_.find(iterRight->second);
    if (iterLeft == leftMap_.end())
      return false;

    rightMap_.erase(iterRight);
    leftMap_.erase(iterLeft);
    return true;
  }

  auto getAllLeft() -> vector<leftElemType> {
    shared_lock<shared_timed_mutex> lk(mtx_);
    vector<leftElemType> vec;
    for (const auto &_ : leftMap_) {
      vec.push_back(_);
    }
    return std::move(vec);
  }

private:
  leftMapType leftMap_;
  rightMapType rightMap_;
  mutable shared_timed_mutex mtx_;
};

template <typename key_t, typename value_t> class easyMuliMap {
public:
  using keyType = key_t;
  using valueType = value_t;
  using mapType = std::multimap<keyType, valueType>;
  using eleType = typename mapType::value_type;

public:
  easyMuliMap() {}
  ~easyMuliMap() { _m.clear(); }

  bool insert(const eleType &e) {
    if (isExist(e))
      return false;
    std::unique_lock<std::shared_timed_mutex> lk(_mtx);
    _m.insert(e);
    return true;
  }

  void remove(const eleType &e) {
    std::unique_lock<std::shared_timed_mutex> lk(_mtx);
    auto range = _m.equal_range(e.first);
    for (auto it = range.first; it != range.second; ++it) {
      if (it->second == e.second) {
        _m.erase(it);
        break;
      }
    }
  }

  void remove(const keyType &ke) {
    std::unique_lock<std::shared_timed_mutex> lk(_mtx);
    auto range = _m.equal_range(ke);
    for (auto it = range.first; it != range.second; ++it) {
      _m.erase(it);
    }
  }

  bool isExist(const eleType &ele) const {
    std::shared_lock<std::shared_timed_mutex> lk(_mtx);
    auto range = _m.equal_range(ele.first);
    for (auto it = range.first; it != range.second; ++it) {
      if (it->second == ele.second) {
        return true;
      }
    }
    return false;
  }

  int64_t size() const {
    std::shared_lock<std::shared_timed_mutex> lk(_mtx);
    return _m.size();
  }

private:
  mapType _m;
  mutable std::shared_timed_mutex _mtx;
};

} // namespace hyperex::magicMap
#endif // __HYPERMAP_H__